﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;
using Isuka.Auxiliaries;

namespace Isuka.Collections
{
    [Serializable]
    public class MDCollection<TKey1, TKey2, TValue>
        : Dictionary<TKey1, IDictionary<TKey2, TValue>>
        , IDictionary<TKey1, IDictionary<TKey2, TValue>>
    {
        readonly Func<IDictionary<TKey2, TValue>> factory;

        public MDCollection()
            : this(() => new Dictionary<TKey2, TValue>())
        { }

        public MDCollection(Func<IDictionary<TKey2, TValue>> factory)
        {
            Contract.Requires<ArgumentNullException>(factory != null, "factory");
            this.factory = factory;
        }

        [ContractVerification(false)]
        protected MDCollection(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
            info.InitValue("factory", out factory);
            if (factory == null)
                factory = () => new Dictionary<TKey2, TValue>();
        }

        public TValue this[TKey1 key1, TKey2 key2]
        {
            get
            {
                var first = base[key1];
                if (first == null)
                {
                    throw new KeyNotFoundException(string.Format("Key1 : {0}", key1));
                }
                return first[key2];
            }
            set
            {
                SetValue(key1, key2, value);
            }
        }

        public void SetValue(TKey1 key1, TKey2 key2, TValue value)
        {
            var inner = default(IDictionary<TKey2, TValue>);
            if (!TryGetValue(key1, out inner) || inner == null)
            {
                inner = Gate.NotNull(factory());
                Add(key1, inner);
            }
            inner.Add(key2, value);
        }

        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("factory", factory);
            base.GetObjectData(info, context);
        }

        #region ContractInvariantMethod

        [ContractInvariantMethod]
        [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
        private void ObjectInvariant()
        {
            Contract.Invariant(factory != null);
        }

        #endregion

    }
}
